/*
Copyright (C) 2022-2024 Inspur Corp.

SPDX-License-Identifier: Apache-2.0
*/
package service

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"os"
	"path/filepath"
	"reflect"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/360EntSecGroup-Skylar/excelize"
	"github.com/emicklei/go-restful"
	"github.com/go-xorm/xorm"
	"github.com/pkg/errors"
	"github.com/tjfoc/gmsm/sm3"

	"icep/bean/entity"
	"icep/bean/param"
	"icep/chain/client"
	"icep/common/constant"
	"icep/common/crypto/gm"
	"icep/common/file"
	"icep/common/upload"
	"icep/common/utils"
	"icep/common/uuid"
)

//上链数据结构
type evidenceDesc struct {
	EvidenceId   string      `json:"evidenceId"`
	EvidenceName string      `json:"evidenceName"`
	ObtainUrl    string      `json:"obtainUrl"`
	Description  string      `json:"description,omitempty"`
	Signature    string      `json:"signature"`
	TotalHash    string      `json:"totalHash"`
	FileList     *[]fileDesc `json:"fileList"`
}

//单个文件信息
type fileDesc struct {
	FileName string `json:"fileName"`
	FileHash string `json:"fileHash"`
	FileUrl  string `json:"fileUrl"`
}

// ParseTemplate 解析批量取证excel模板，返回所有参数
func (is *IcepService) ParseTemplate(tempFile *multipart.FileHeader) ([]*param.CreateObtainEvidenceParam, error) {
	var para []*param.CreateObtainEvidenceParam
	fileReader, err := tempFile.Open()
	if err != nil {
		return nil, err
	}
	defer fileReader.Close()
	//获取表格中的所有行
	rows, err := getRows(fileReader)
	if err != nil {
		return nil, err
	}
	if len(rows) == 0 {
		return nil, errors.New("template content is empty!")
	}

	fieldIdx := tag2Field(param.CreateObtainEvidenceParam{}, "xlsx")
	err = verifyColumnName(rows[0], fieldIdx)
	if err != nil {
		return nil, err
	}

	para = rows2Struct(rows, fieldIdx)

	return para, nil
}

//验证取证模板的列名
func verifyColumnName(row []string, names map[string]int) error {
	for name, _ := range names {
		flag := false
		for _, cName := range row {
			if cName == name {
				flag = true
			}
		}
		if !flag {
			return errors.New("template column name error!")
		}
	}
	return nil
}

//获取表格中的所有行
func getRows(file io.Reader) ([][]string, error) {
	f, err := excelize.OpenReader(file)
	if err != nil {
		return nil, err
	}
	sheetMap := f.GetSheetMap()
	return f.GetRows(sheetMap[1]), nil
}

//获取struct的标签，返回结果中key为标签的值，value为索引
func tag2Field(v interface{}, tagKey string) map[string]int {
	u := reflect.TypeOf(v)
	numField := u.NumField()
	tag2fieldIndex := map[string]int{}
	for i := 0; i < numField; i++ {
		f := u.Field(i)
		tagValue, ok := f.Tag.Lookup(tagKey)
		if ok {
			tag2fieldIndex[tagValue] = i
		} else {
			continue
		}
	}
	return tag2fieldIndex
}

//获取struct的标签，返回结果中key为索引，value为标签的值
func tag2Field_2(v interface{}, tagKey string) map[int]string {
	u := reflect.TypeOf(v)
	numField := u.NumField()
	tag2fieldIndex := map[int]string{}
	for i := 0; i < numField; i++ {
		f := u.Field(i)
		tagValue, ok := f.Tag.Lookup(tagKey)
		if ok {
			tag2fieldIndex[i] = tagValue
		} else {
			continue
		}
	}
	return tag2fieldIndex
}

//将表格中的每一行转换为param.CreateObtainEvidenceParam
func rows2Struct(rows [][]string, tag2fieldIndex map[string]int) []*param.CreateObtainEvidenceParam {
	var data []*param.CreateObtainEvidenceParam
	// 第一行为列名，对应struct中的标签value
	head := rows[0]
	for _, row := range rows[1:] {
		para := &param.CreateObtainEvidenceParam{}
		rv := reflect.ValueOf(para).Elem()
		for i := 0; i < len(row); i++ {
			colCell := row[i]
			// 通过 tag 取到结构体字段下标
			fieldIndex, ok := tag2fieldIndex[head[i]]
			if !ok {
				continue
			}
			colCell = strings.Trim(colCell, " ")
			// 通过字段下标找到字段映射对象
			v := rv.Field(fieldIndex)
			// 根据字段的类型，选择适合的赋值方法
			switch v.Kind() {
			case reflect.String:
				v.SetString(colCell)
			}
		}
		data = append(data, para)
	}
	return data
}

// ReqPara2Struct 将Request中的参数映射至对应的struct
func ReqPara2Struct(req *restful.Request, v interface{}) interface{} {
	fieldIdx := tag2Field_2(v, "json")
	u := reflect.TypeOf(v)
	var rv reflect.Value
	var proc = func() {
		for i := 0; i < len(fieldIdx); i++ {
			//去除“omitempty”
			paraName := strings.Split(fieldIdx[i], ",")[0]
			v := rv.FieldByName(FirstToUpper(paraName))
			v.SetString(req.QueryParameter(paraName))
		}
	}

	switch u.Name() {
	case "ObtainListParam":
		para := param.ObtainListParam{}
		rv = reflect.ValueOf(&para).Elem()
		proc()
		return para
	case "CreateObtainEvidenceParam":
		para := param.CreateObtainEvidenceParam{}
		rv = reflect.ValueOf(&para).Elem()
		proc()
		return para
	default:
		return nil
	}
}

// FirstToUpper 将字符串首字母变成大写
func FirstToUpper(s string) string {
	b := ([]byte(s))[0]
	if b >= 'a' && b <= 'z' {
		b = b - ('a' - 'A')
	}
	return string(b) + s[1:]
}

// VerifyUrl 验证URL，使其包含协议名
func VerifyUrl(tk *task) string {
	if !strings.HasPrefix(tk.para.ObtainUrl, "http://") &&
		!strings.HasPrefix(tk.para.ObtainUrl, "https://") {
		return strings.Join([]string{"http://", tk.para.ObtainUrl}, "")
	}
	return tk.para.ObtainUrl
}

// VerifyObtainEvidencePara 验证取证请求参数
func VerifyObtainEvidencePara(data interface{}) (passed []*param.CreateObtainEvidenceParam,
	notPassed []*param.CreateObtainEvidenceParam, errs map[int]error) {
	errs = make(map[int]error, 0)

	switch paras := data.(type) {
	//取证参数验证
	case param.CreateObtainEvidenceParam:
		err := verifyObtainEvidencePara(paras)
		if err != nil {
			errs[1] = err
			return
		}

	//批量取证参数验证
	case []*param.CreateObtainEvidenceParam:
		for i, para := range paras {
			err := verifyObtainEvidencePara(*para)
			if err != nil {
				if err.Error() == "para is nil" {
					continue
				}
				notPassed = append(notPassed, para)
				errs[i+1] = err
				continue
			}
			passed = append(passed, para)
		}
	}
	return
}

// 验证取证请求参数
func verifyObtainEvidencePara(para param.CreateObtainEvidenceParam) error {
	if para.EvidenceName == "" && para.ObtainUrl == "" {
		return errors.New("para is nil")
	}
	if para.EvidenceName == "" {
		log.Error("evidence name can not be empty!")
		return errors.New("取证名称不能为空!")
	}

	if utf8.RuneCountInString(para.EvidenceName) > 50 {
		log.Error("evidence name is too long!")
		return errors.New("取证名称应小于50个字符！")
	}

	if para.ObtainUrl == "" {
		log.Error("URL can not be empty!")
		return errors.New("取证网址不能为空！")
	}

	if utf8.RuneCountInString(para.ObtainUrl) > 100 {
		log.Error("URL is too long!")
		return errors.New("取证网址应小于100个字符！")
	}

	if utf8.RuneCountInString(para.Description) > 150 {
		log.Error("description is too long!")
		return errors.New("取证备注说明应小于150个字符！")
	}

	return nil
}

// CombineErr 组合取证参数验证后的错误信息
func CombineErr(errs map[int]error) string {
	errStr := make([]string, 0)
	var keys []int
	for key, _ := range errs {
		keys = append(keys, key)
	}
	//对key排序
	sort.Ints(keys)

	for _, key := range keys {
		des := fmt.Sprintf("第%v个取证任务错误：%s", key, errs[key].Error())
		errStr = append(errStr, des)
	}
	return strings.Join(errStr, ";")
}

func (is *IcepService) GetUserPrvKey(userId string) (string, error) {
	if userId == "" {
		return "", errors.New("user id can not be empty")
	}
	//用户表
	user := make([]*entity.IcepUser, 0)
	err := is.DbEngine.Where("id = ?", userId).Find(&user)
	if err != nil {
		return "", errors.Errorf("query user error: %s", err)
	}
	return gm.DecryptPrvKey(user[0].PrvKey), nil
}

func (is *IcepService) GetTenantPrvKey(tenantId string) (string, error) {
	if tenantId == "" {
		return "", errors.New("tenant id can not be empty")
	}
	//租户身份表
	var tenant []*entity.IcepTenantIdentity
	err := is.DbEngine.Where("tenant_id = ? and status = '1' ", tenantId).Find(&tenant)
	if err != nil {
		return "", errors.Errorf("query tenant identity error: %s", err)
	}
	if len(tenant) == 0 {
		return "", errors.New("the tenant does not exist!")
	}
	return tenant[0].PrvKey, nil
}

//combine SQL Statement
func combineSql(statement string, condition *string, para string, args *[]interface{}) {
	*condition = utils.StrJoin(*condition, statement)
	if para != "" {
		*args = append(*args, para)
	}
}

// GetDataHash 计算文件、字符串哈希
func GetDataHash(data interface{}) (string, []string, []byte, error) {
	filesHash := make([]string, 0)
	hash := sm3.New()

	switch res := data.(type) {
	case []*multipart.FileHeader:
		for _, fileHeader := range res {
			fileReader, err := fileHeader.Open()
			if err != nil {
				return "", nil, nil, errors.Errorf("open file error: %v", err)
			}
			if _, err := io.Copy(hash, fileReader); err != nil {
				return "", nil, nil, errors.Errorf("get file fash error: %v", err)
			}
			err = fileReader.Close()
			if err != nil {
				return "", nil, nil, err
			}
			hashBytes := hash.Sum(nil)
			filesHash = append(filesHash, hex.EncodeToString(hashBytes))
			hash = sm3.New()
		}
		return CombineHash(filesHash), filesHash, nil, nil

	case string:
		hash.Write([]byte(res))
		return "", nil, []byte(hex.EncodeToString(hash.Sum(nil))), nil

	case []byte:
		hash.Write(res)
		return "", nil, []byte(hex.EncodeToString(hash.Sum(nil))), nil
	}
	return "", nil, nil, errors.New("This type is not supported!")
}

// CombineHash 将多个哈希组合成字符串
func CombineHash(filesHash []string) string {
	if len(filesHash) == 1 {
		return filesHash[0]
	}

	tempHash := make([]string, 0)
	for _, fileHash := range filesHash {
		tempHash = append(tempHash, fmt.Sprintf("\"%s\"", fileHash))
	}
	return fmt.Sprintf("[%s]", strings.Join(tempHash, ","))
}

// CreatTempDir 创建临时目录存储文件
func creatTempDir(evidenceId string) (string, error) {
	tempDir, err := ioutil.TempDir(os.TempDir(), evidenceId)
	if err != nil {
		return "", errors.Errorf("Failed to create temporary directory: %v", err)
	}
	return tempDir, nil
}

// UploadVideoFiles 将录屏文件上传云存储
func UploadVideoFiles(evidenceId string, files []*multipart.FileHeader) ([]string, error) {
	tempDir, err := creatTempDir(evidenceId)
	if err != nil {
		return nil, err
	}
	log.Debugf("creat temporary directory for video files: %v", tempDir)

	//上传云存储后删除文件
	defer func(path string) {
		err = os.RemoveAll(path)
		if err != nil {
			log.Errorf("Failed to remove temporary directory: %v", err)
		}
	}(tempDir)
	//验证文件名不为空
	for _, fileHeader := range files {
		if fileHeader.Filename == "" {
			return nil, errors.New("filename can not be empty!")
		}
	}

	uploadClient := upload.GetUploadClient()

	urls := make([]string, 0)
	for i, fileHeader := range files {
		filename := fileHeader.Filename
		size := fileHeader.Size
		log.Debugf("[%v/%v]--fileName: %v, fileSize: %v", i+1, len(files), filename, size)
		//文件保存至临时目录
		err := file.SaveUploadedFile(fileHeader, filepath.Join(tempDir, filename))
		if err != nil {
			return nil, errors.Errorf("save video file error: %v", err)
		}
		//上传文件获取URL
		url, err := uploadClient.UploadFile(filepath.Join(tempDir, filename), evidenceId+"/"+strconv.Itoa(i+1)+"_"+filename)
		if err != nil {
			return nil, err
		}
		log.Debugf("[%v/%v]--fileName: %v, url: %v", i+1, len(files), filename, url)
		urls = append(urls, url)
	}
	return urls, nil
}

func InsertSignature(dbEngine *xorm.Engine, evidenceId, signature string) error {
	now := time.Now()
	signatureEntity := entity.IcepSignature{
		Id:          uuid.Generate(),
		EvidenceId:  evidenceId,
		Signature:   signature,
		CreatedTime: now,
		UpdatedTime: now,
	}

	_, err := dbEngine.InsertOne(signatureEntity)
	if err != nil {
		return errors.Errorf("insert signature error: %s", err)
	}
	return nil
}

func InsertObtainEvidenceData(dbEngine *xorm.Engine, userId, evidenceId, identityId, ObtainType string, para *param.CreateObtainEvidenceParam) error {
	now := time.Now()
	evidenceData := entity.IcepObtainEvidence{
		Id:           evidenceId,
		UserId:       userId,
		IdentityId:   identityId,
		EvidenceName: para.EvidenceName,
		Description:  para.Description,
		ObtainUrl:    para.ObtainUrl,
		ObtainType:   ObtainType,
		Status:       constant.ObtainStatusProcessing,
		CreatedTime:  now,
		UpdatedTime:  now,
	}

	_, err := dbEngine.InsertOne(evidenceData)
	if err != nil {
		return errors.Errorf("insert obtain evidence error: %s", err)
	}
	return nil
}

func InsertObtainEvidenceDetail(dbEngine *xorm.Engine, evidenceId string, file, hash, url interface{}) error {
	now := time.Now()
	detail := make([]*entity.IcepObtainEvidenceDetail, 0)

	switch res := file.(type) {
	case []*multipart.FileHeader:
		filesHash, ok := hash.([]string)
		if !ok {
			return errors.New("filesHash input type error!")
		}
		urls, ok := url.([]string)
		if !ok {
			return errors.New("urls input type error!")
		}
		if len(res) != len(filesHash) || len(res) != len(urls) {
			return errors.New("length is not equal, parameter is illegal!")
		}
		for i, fileHeader := range res {
			detail = append(detail, &entity.IcepObtainEvidenceDetail{
				Id:          uuid.Generate(),
				ObtainId:    evidenceId,
				FileName:    fileHeader.Filename,
				FileHash:    filesHash[i],
				FileUrl:     urls[i],
				CreatedTime: now,
				UpdatedTime: now,
			})
		}
	case string:
		fileHash, ok := hash.(string)
		if !ok {
			return errors.New("fileHash input type error!")
		}

		fileUrl, ok := url.(string)
		if !ok {
			return errors.New("fileUrl input type error!")
		}
		detail = append(detail, &entity.IcepObtainEvidenceDetail{
			Id:          uuid.Generate(),
			ObtainId:    evidenceId,
			FileName:    res,
			FileHash:    fileHash,
			FileUrl:     fileUrl,
			CreatedTime: now,
			UpdatedTime: now,
		})
	}
	_, err := dbEngine.Insert(detail)
	if err != nil {
		return errors.Errorf("insert obtain evidence detail error: %s", err)
	}
	log.Debugf("insert obtain evidence detail, evidenceId: %v", evidenceId)

	return nil
}

func InsertChain(client *client.IchainClient, evidenceId, signature string, totalHash string, para param.CreateObtainEvidenceParam, file, hash, url interface{}) (string, string, error) {
	var evidenceData evidenceDesc
	fileData := make([]fileDesc, 0)
	switch res := file.(type) {
	case []*multipart.FileHeader:
		filesHash, ok := hash.([]string)
		if !ok {
			return "", "", errors.New("filesHash input type error!")
		}
		urls, ok := url.([]string)
		if !ok {
			return "", "", errors.New("urls input type error!")
		}

		if len(res) != len(filesHash) || len(res) != len(urls) {
			return "", "", errors.New("length is not equal, insert parameter is illegal!")
		}
		//组装文件描述信息
		for i, fileHeader := range res {
			fileData = append(fileData, fileDesc{
				FileName: fileHeader.Filename,
				FileHash: filesHash[i],
				FileUrl:  urls[i],
			})
		}

	case string:
		fileHash, ok := hash.(string)
		if !ok {
			return "", "", errors.New("filesHash input type error!")
		}

		fileUrl, ok := url.(string)
		if !ok {
			return "", "", errors.New("fileUrl input type error!")
		}

		fileData = append(fileData, fileDesc{
			FileName: res,
			FileHash: fileHash,
			FileUrl:  fileUrl,
		})
	}
	//组装上链数据
	evidenceData = evidenceDesc{
		EvidenceId:   evidenceId,
		EvidenceName: para.EvidenceName,
		ObtainUrl:    para.ObtainUrl,
		Description:  para.Description,
		Signature:    signature,
		TotalHash:    totalHash,
		FileList:     &fileData,
	}
	evidenceDataBytes, _ := json.Marshal(evidenceData)
	log.Debugf("write to chain, key: %v, value: %v", evidenceId, string(evidenceDataBytes))

	//上链，得到交易id
	txId, _ := client.PutObtainEvidence(evidenceId, string(evidenceDataBytes))
	if txId == "" {
		return "", "", errors.New("write to chain error")
	}
	log.Debugf("get txId: %v, evidenceId: %v", txId, evidenceId)
	//查询交易时间
	txTime := time.Now().Format(constant.TimeLayout)
	transInfo := client.GetTransactionInfoById(txId)
	if transInfo != nil {
		txTime = transInfo.TxTime
		log.Debugf("get txTime: %v, evidenceId: %v", txTime, evidenceId)
	}
	return txId, txTime, nil
}

// UpdateObtainEvidenceData 更新取证表
func UpdateObtainEvidenceData(dbEngine *xorm.Engine, evidenceId, identityId, status, letterUrl string, res *Result) error {
	log.Debugf("update obtain evidence data, evidenceId: %v", evidenceId)
	now := time.Now()
	var evidenceData entity.IcepObtainEvidence

	switch status {
	case constant.ObtainStatusSuccess:
		evidenceData = entity.IcepObtainEvidence{
			Status:       constant.ObtainStatusSuccess,
			EvidenceCert: letterUrl,
			TxId:         res.TxId,
			TxTime:       res.TxTime,
			UpdatedTime:  now,
		}
	case constant.ObtainStatusFailure:
		//更新前先查询取证ID是否存在
		data := make([]*entity.IcepObtainEvidence, 0)
		err := dbEngine.Where("id = ?", evidenceId).Find(&data)
		if err != nil {
			return errors.Errorf("query obtain evidence error: %s", err)
		}

		//没有记录则插入
		if len(data) == 0 {
			evidenceData = entity.IcepObtainEvidence{
				Id:          uuid.Generate(),
				UserId:      res.userId,
				IdentityId:  identityId,
				ObtainType:  res.taskType,
				Status:      constant.ObtainStatusFailure,
				CreatedTime: now,
				UpdatedTime: now,
			}
			if res.para != nil {
				evidenceData.EvidenceName = res.para.EvidenceName
				evidenceData.Description = res.para.Description
				evidenceData.ObtainUrl = res.para.ObtainUrl
			}
			_, err := dbEngine.InsertOne(&evidenceData)
			if err != nil {
				return errors.Errorf("update obtain evidence error: %s", err)
			}
			return nil
		}
		evidenceData = entity.IcepObtainEvidence{
			Status:      constant.ObtainStatusFailure,
			UpdatedTime: now,
		}
	}

	_, err := dbEngine.Where("id = ?", evidenceId).Update(evidenceData)
	if err != nil {
		return errors.Errorf("update obtain evidence error: %s", err)
	}
	return nil
}

// GetWebpageSnapshot 获取网页快照
func (is *IcepService) GetWebpageSnapshot(userId, evidenceId, userPrvKey string,
	para *param.CreateObtainEvidenceParam, client *client.IchainClient, letterParam *param.EvidenceLetterParam) {
	//将取证信息添加至任务队列
	GetTaskQueue().Push(userId, evidenceId, userPrvKey, para, constant.ObtainTypeHtml, client, letterParam)
}

// GetWebpageScreenshot 获取网页截图
func (is *IcepService) GetWebpageScreenshot(userId, evidenceId, userPrvKey string,
	para *param.CreateObtainEvidenceParam, client *client.IchainClient, letterParam *param.EvidenceLetterParam) {
	//将取证信息添加至任务队列
	GetTaskQueue().Push(userId, evidenceId, userPrvKey, para, constant.ObtainTypePict, client, letterParam)
}

// DealWithErr 处理错误
func DealWithErr(dbEngine *xorm.Engine, evidenceId string) {
	err := UpdateObtainEvidenceData(dbEngine, evidenceId, "", constant.ObtainStatusFailure, "", nil)
	if err != nil {
		log.Errorf("update obtain evidence data err: %v", err)
		return
	}
	log.Debug("update obtain evidence data success")
}
